package com.ruoyi.vote.admin.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.executor.RedissonLockExecutor;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.ruoyi.common.enums.UserType;
import com.ruoyi.common.exception.vote.VoteException;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.mall.enums.OrderInfoEnum;
import com.ruoyi.vote.admin.domain.VoteSign;
import com.ruoyi.vote.admin.domain.bo.VotePrizeReceiveBo;
import com.ruoyi.vote.admin.domain.dto.ActivityJpOptionDto;
import com.ruoyi.vote.admin.domain.vo.VotePrizeVo;
import com.ruoyi.vote.admin.service.IVoteActivityService;
import com.ruoyi.vote.admin.service.IVotePrizeReceiveService;
import com.ruoyi.vote.admin.service.IVotePrizeService;
import com.ruoyi.vote.admin.service.IVoteSignService;
import com.ruoyi.vote.app.domain.request.VoteActivityReq;
import com.ruoyi.vote.common.constants.ExamineConstants;
import com.ruoyi.vote.common.constants.VoteConstants;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.vote.common.utils.KeyUtils;
import com.ruoyi.vote.common.utils.VoteCheckUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.ruoyi.vote.admin.domain.bo.VoteActivityBo;
import com.ruoyi.vote.admin.domain.vo.VoteActivityVo;
import com.ruoyi.vote.admin.domain.VoteActivity;
import com.ruoyi.vote.admin.mapper.VoteActivityMapper;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * 投票 - 活动管理Service业务层处理
 *
 * @author 筑巢
 * @date 2023-07-17
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class VoteActivityServiceImpl implements IVoteActivityService {

    private final VoteActivityMapper baseMapper;
    private final IVoteSignService signService;
    private final IVotePrizeReceiveService prizeReceiveService;
    private final IVotePrizeService prizeService;
    private final LockTemplate lockTemplate;

    /**
     * 初始化活动缓存
     */
    @Override
    public void loadingActivityCache() {
        getAllEvents().forEach(this::setCache);
    }

    /**
     * 活动详情 (实时数据)
     */
    @Override
    public VoteActivityVo queryById(Long id){
        VoteActivityVo activityVo = baseMapper.selectVoById(id);
        // 活动不存在
        if(ObjectUtil.isEmpty(activityVo)){
            throw new VoteException("vote.activity.doesNotExist");
        }
        // 设置参赛人数
        activityVo.setSignNum(signService.count(new LambdaQueryWrapper<VoteSign>()
            .eq(VoteSign::getAId,id)
            .eq(VoteSign::getStatus,ExamineConstants.SUCCESS)
        ));
        // 已结算
        if (activityVo.getJsStatus().equals(VoteConstants.VOTE_STATUS_ERROR)){
            return activityVo;
        }
        String eventTotalKey = KeyUtils.formatEventTotalKey(String.valueOf(activityVo.getId()));
        activityVo.setTicket(RedisUtils.getAtomicValue(eventTotalKey));
        if (StpUtil.isLogin() && UserType.XCX_MEMBER.equals(LoginHelper.getUserType())) {
            // 是否参赛
            activityVo.setIsJoin(signService.count(new LambdaQueryWrapper<VoteSign>()
                .eq(VoteSign::getAId,id)
                .eq(VoteSign::getMId, LoginHelper.getUserId())
                .and(w -> w.eq(VoteSign::getStatus, ExamineConstants.SUCCESS).or().eq(VoteSign::getStatus, ExamineConstants.REVIEW))
            ));
        }
        return activityVo;
    }

    /**
     * 后台查询 - 活动列表  (延时)
     */
    @Override
    public TableDataInfo<VoteActivityVo> queryPageList(VoteActivityReq req, PageQuery pageQuery) {
        QueryWrapper<VoteActivity> lqw = buildQueryWrapper(req);
        Page<VoteActivityVo> result = baseMapper.selectPageVoList(pageQuery.build(), lqw);
        result.setRecords(result.getRecords().stream().peek(item ->
            item.setSignNum(signService.count(new LambdaQueryWrapper<VoteSign>()
                .eq(VoteSign::getAId,item.getId())
                .eq(VoteSign::getStatus,ExamineConstants.SUCCESS)))
        ).collect(Collectors.toList()));
        return TableDataInfo.build(result);
    }

    private QueryWrapper<VoteActivity> buildQueryWrapper(VoteActivityReq bo) {
        // 热门、经典、标题、状态、结算状态、投票开始-活动结束区间、
        Map<String, Object> params = bo.getParams();
        QueryWrapper<VoteActivity> lqw = Wrappers.query();
        lqw.eq("va.del_flag", VoteConstants.VOTE_STATUS_SUCCESS);
        lqw.eq(ObjectUtil.isNotNull(bo.getId()), "va.id", bo.getId());
        lqw.like(StringUtils.isNotBlank(bo.getTitle()), "va.title", bo.getTitle());
        lqw.eq(ObjectUtil.isNotNull(bo.getMId()),"vs.m_id",bo.getMId());
        lqw.eq(ObjectUtil.isNotNull(bo.getMName()),"vs.name",bo.getMName());

        if (UserType.XCX_MEMBER.equals(LoginHelper.getUserType())){ // 小程序
            lqw.eq("va.status", VoteConstants.VOTE_STATUS_SUCCESS);
        }else{
            lqw.eq(bo.getIsAuditing() != null, "va.is_auditing", bo.getIsAuditing());
            lqw.eq(bo.getAType() != null, "va.a_type", bo.getAType());
            lqw.eq(bo.getStatus() != null, "va.status", bo.getStatus());
            // 结算
            lqw.eq(bo.getJsStatus() != null, "va.js_status", bo.getJsStatus());
        }

        if(Objects.equals(bo.getVoteStatus(), VoteConstants.VOTE_STATUS_SUCCESS)){ // 进行中活动
            lqw.gt("va.balance_time" , DateUtils.getDate());
            lqw.eq("va.js_status",VoteConstants.VOTE_STATUS_SUCCESS);
        }else if(Objects.equals(bo.getVoteStatus(), VoteConstants.VOTE_STATUS_ERROR)){ // 活动结束
            lqw.and(wapper -> wapper.lt("va.balance_time" , DateUtils.getDate())
                .or()
                .eq("va.js_status",VoteConstants.VOTE_STATUS_ERROR));
        }
        lqw.groupBy("va.id");

        return lqw;
    }

    /**
     * 创建活动
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean insertByBo(VoteActivityBo bo) {
        log.info("--- 活动管理 - 创建活动数据:{}",bo);
        VoteActivity activity = BeanUtil.toBean(bo, VoteActivity.class);
        boolean flag = baseMapper.insert(activity) > 0;
        if (flag) {
            bo.setId(activity.getId());
            setCache(activity);
            // 注册活动开始通知
            long startTime = DateUtils.getSecondsFromNow(bo.getVoteStartTime());
            long stopTime = DateUtils.getSecondsFromNow(bo.getBalanceTime());
            if (startTime < 0 || stopTime < 0){
                throw new VoteException("vote.activity.time.error");
            }
            RedisUtils.setCacheObject(KeyUtils.formatStartNotice(activity.getId()),VoteConstants.VOTE_STATUS_SUCCESS, Duration.ofSeconds(startTime));
            log.info("--- 创建活动 ,注册活动开始通知倒计时秒: {}",startTime);
            // 注册活动结束通知
            RedisUtils.setCacheObject(KeyUtils.formatStopNotice(activity.getId()),VoteConstants.VOTE_STATUS_SUCCESS, Duration.ofSeconds(stopTime));
            log.info("--- 创建活动 ,注册活动停止通知倒计时秒: {}",stopTime);
        }
        return flag;
    }

    /**
     * 修改活动
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateByBo(VoteActivity activity) {
        log.info("--- 活动管理 - 修改活动数据: {} ", activity);
        boolean flag = baseMapper.updateById(activity) > 0;
        if(flag){
            setCache(activity);
        }
        return flag;
    }

    /**
     * 批量删除活动
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids) {
        log.info("--- 活动管理 - 修改活动数据: {} ", ids);
        return baseMapper.deleteBatchIds(ids) > 0;
    }

    /**
     * 同步所有活动缓存
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void calculateAndStoreAllRankings() {
        // 获取所有活动的活动ID列表
        getAllEvents().stream()
            .map(voteSign -> String.valueOf(voteSign.getId()))
            .collect(Collectors.toSet()).forEach(this::calculateRankingByaId);
    }

    /**
     * 同步活动排名以及票数
     *
     * @param eventId
     * @return
     */
    @Async
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean calculateRankingByaId(String eventId) {
        final LockInfo lockInfo = lockTemplate.lock(eventId, 500L, 5000L, RedissonLockExecutor.class);
        if (null == lockInfo) {
            log.info("--业务处理中--");
            return false;
        }
        try {
            log.info("--- 活动管理 - 同步排名活动id: {} ", eventId);
            // 排名更新
            signService.getEventRankings(eventId);
            // 活动总票数更新
            synchActivityTicket(eventId);
        } finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }
        return true;
    }

    /**
     * 活动结算
     *
     * @param eventId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean activitySettlement(String eventId) {
        VoteActivity activity = baseMapper.selectById(eventId);
        if (activity.getJsStatus().equals(VoteConstants.VOTE_STATUS_ERROR)) {
            throw new VoteException("vote.activity.ended.js",eventId);
        }
        // todo 结算活动
        Boolean flg = this.calculateRankingByaId(eventId);

        if(flg){
            // 添加奖品数据
            List<ActivityJpOptionDto> jpOptionDtoList = JSON.parseArray(activity.getJpOption(), ActivityJpOptionDto.class);
            // 获取前三名选手
            List<VoteSign> ranking = signService.getRanking(Long.valueOf(eventId),jpOptionDtoList.size());
            for (int i = 0; i < jpOptionDtoList.size(); i++) {
                if (i == ranking.size()){
                    break;
                }
                ActivityJpOptionDto jpOptionDto = jpOptionDtoList.get(i);
                VotePrizeVo votePrizeVo = prizeService.queryById(jpOptionDto.getJiangpinid());
                VoteSign voteSign = ranking.get(i);

                VotePrizeReceiveBo prizeReceive = new VotePrizeReceiveBo();
                prizeReceive.setAId(Long.valueOf(eventId));
                prizeReceive.setATitle(activity.getTitle());
                prizeReceive.setMId(voteSign.getMId());
                prizeReceive.setPrizeId(votePrizeVo.getId());
                prizeReceive.setPrizeName(jpOptionDto.getName());
                prizeReceive.setPrizeNum(jpOptionDto.getPrizeNum());
                prizeReceive.setName(voteSign.getName());
                prizeReceive.setPhone(voteSign.getPhone());
                prizeReceive.setStatus(VoteConstants.VOTE_STATUS_SUCCESS);
                prizeReceive.setJsTime(DateUtils.getNowDate());
                prizeReceive.setPrizePics(votePrizeVo.getLogo());
                prizeReceive.setFhStatus(OrderInfoEnum.STATUS_1.getValue());
                prizeReceiveService.insertByBo(prizeReceive);
            }

            // 修改活动状态
            activity.setJsStatus(1);
            flg = updateByBo(activity);
            if (flg) deleteVoteCache(eventId);
        }

        return flg;
    }

    /**
     * 热度+1
     *
     * @param id
     */
    @Override
    public void updateWelcomeOne(Long id) {
        try {
            String infoEventKey = KeyUtils.formatInfoEventKey(String.valueOf(id));
            VoteCheckUtils.activityCheck(infoEventKey);
            baseMapper.updateWelcomeOne(id);
            log.error("--- 活动ID:[{}] 热度+1~",id);
        }catch (VoteException e){
            log.error("--- 活动结束:[{}] 热度+0~",id);
        }
    }

    /**
     * 同步活动票数
     * @param eventId
     */
    private boolean synchActivityTicket(String eventId){
        String eventTotalKey = KeyUtils.formatEventTotalKey(eventId);
        String infoEventKey = KeyUtils.formatInfoEventKey(eventId);
        Long ticket = Optional.of(RedisUtils.getAtomicValue(eventTotalKey)).orElse(0L);
        VoteActivity activity = RedisUtils.getCacheObject(infoEventKey);
        activity.setTicket(ticket);
        log.info("--- 活动管理 - 同步活动票数数据: {} ", activity);
        return updateByBo(activity);
    }

    /**
     * 获取进行中的活动
     * @return
     */
    private List<VoteActivity> getAllEvents() {
        return baseMapper.selectList(new LambdaQueryWrapper<VoteActivity>()
            .eq(VoteActivity::getStatus, VoteConstants.VOTE_STATUS_SUCCESS)
            .gt(VoteActivity::getBalanceTime, DateUtils.getTime())
            .eq(VoteActivity::getJsStatus,VoteConstants.VOTE_STATUS_SUCCESS));
    }

    /**
     * 设置活动缓存
     */
    private void setCache(VoteActivity activity){
        String key = KeyUtils.formatInfoEventKey(String.valueOf(activity.getId()));
        log.info("--- 活动管理 - 设置活动缓存KEY: [{}] ", key);
        if (RedisUtils.isExistsObject(key)) RedisUtils.deleteKeys(key);
        RedisUtils.setCacheObject(key,activity);
    }

    /**
     * 删除所有活动相关key
     * @param eventId
     */
    private void deleteVoteCache(String eventId){
        // 删除活动对象缓存
        String infoEventKey = KeyUtils.formatInfoEventKey(eventId);
        String eventSignKey = KeyUtils.formatInfoSignKey(eventId);
        String eventTotalKey = KeyUtils.formatEventTotalKey(eventId);
        String acsKey = KeyUtils.formatAcsKey(eventId,"*");
        String contestantNumberKey = KeyUtils.formatNumberPool(eventId);
        RedisUtils.deleteKeys(infoEventKey);
        RedisUtils.deleteKeys(eventSignKey);
        RedisUtils.deleteKeys(eventTotalKey);
        RedisUtils.deleteKeys(acsKey);
        RedisUtils.deleteKeys(contestantNumberKey);
    }
}
